/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using System.Qizx.Api;
using System.Qizx.Xml;
using System.Xml;
using System.Xml.Schema;
using System.Qizx.XQuery.Fn;
using DataModelException = System.Qizx.Api.DataModelException;
using DateTimeException = System.Qizx.Api.Util.XQDateTimeException;
using DtNodeType = System.Qizx.XQuery.Dt.NodeType;
using EvaluationException = System.Qizx.Api.EvaluationException;
using ItemType = System.Qizx.Api.ItemType;
using NamespaceContext = System.Qizx.Util.NamespaceContext;
using Node = System.Qizx.Api.Node;
using QName = System.Qizx.Api.QName;
using XMLPushStream = System.Qizx.Api.XMLPushStream;
using XQDate = System.Qizx.Api.Util.XQDate;
using XQDateTime = System.Qizx.Api.Util.XQDateTime;
using XQItem = System.Qizx.XQuery.XQItem;
using XQItemType = System.Qizx.XQuery.XQItemType;
namespace System.Qizx.Xdm {

    /// <summary>
    /// Abstract base for actual Node implementations.
    /// </summary>
    [Serializable]
    public abstract class BasicNode : Node, XQItem
        /* not quite true */
    {
        
        internal static XdmSchemaInfo _NotKnownSchemaInfo;
        internal static XdmSchemaInfo _ValidSchemaInfo;
        internal static XdmSchemaInfo _InvalidSchemaInfo;

        static BasicNode() {
            _NotKnownSchemaInfo = new XdmSchemaInfo();
            _NotKnownSchemaInfo.Validity = XmlSchemaValidity.NotKnown;
            _ValidSchemaInfo = new XdmSchemaInfo();
            _ValidSchemaInfo.Validity = XmlSchemaValidity.Valid;
            _InvalidSchemaInfo = new XdmSchemaInfo();
            _InvalidSchemaInfo.Validity = XmlSchemaValidity.Invalid;
        }

        public virtual IXdmSchemaInfo SchemaInfo {
            get {
                return _NotKnownSchemaInfo;
            }
        }

        public abstract XmlSchemaSet Schemas { get; }

        virtual public ItemType ValueType {
            get {
                return ItemType;
            }

        }


        virtual public XQItemType ItemType {
            get {
                try {
                    //HACK CoShare
                    IXdmSchemaInfo schemaInfo = this.SchemaInfo;
                    if (schemaInfo != null && schemaInfo.DataType != null) {
                        switch (schemaInfo.DataType.TypeCode) {
                            case XmlTypeCode.Decimal:
                                return XQItemType.DECIMAL;
                            case XmlTypeCode.Date:
                                return XQItemType.DATE;
                            case XmlTypeCode.DateTime:
                                return XQItemType.DATE_TIME;
                            case XmlTypeCode.Time:
                                return XQItemType.TIME;
                            case XmlTypeCode.Int:
                                return XQItemType.INT;
                            case XmlTypeCode.Integer:
                                return XQItemType.INTEGER;
                            case XmlTypeCode.Long:
                                return XQItemType.LONG;
                            case XmlTypeCode.UnsignedLong:
                                return XQItemType.UNSIGNED_LONG;
                            case XmlTypeCode.Short:
                                return XQItemType.SHORT;
                            case XmlTypeCode.UnsignedShort:
                                return XQItemType.UNSIGNED_SHORT;
                            case XmlTypeCode.NonNegativeInteger:
                                return XQItemType.NON_NEGATIVE_INTEGER;
                            case XmlTypeCode.NonPositiveInteger:
                                return XQItemType.NON_POSITIVE_INTEGER;
                            case XmlTypeCode.PositiveInteger:
                                return XQItemType.POSITIVE_INTEGER;
                            case XmlTypeCode.Byte:
                                return XQItemType.BYTE;
                            case XmlTypeCode.UnsignedByte:
                                return XQItemType.UNSIGNED_BYTE;
                            case XmlTypeCode.Double:
                                return XQItemType.DOUBLE;
                            case XmlTypeCode.Float:
                                return XQItemType.FLOAT;
                            case XmlTypeCode.GDay:
                                return XQItemType.G_DAY;
                            case XmlTypeCode.GMonth:
                                return XQItemType.G_MONTH;
                            case XmlTypeCode.GMonthDay:
                                return XQItemType.G_MONTH_DAY;
                            case XmlTypeCode.GYear:
                                return XQItemType.G_YEAR;
                            case XmlTypeCode.GYearMonth:
                                return XQItemType.G_YEAR_MONTH;
                            case XmlTypeCode.DayTimeDuration:
                                return XQItemType.DAY_TIME_DURATION;
                            case XmlTypeCode.YearMonthDuration:
                                return XQItemType.YEAR_MONTH_DURATION;
                            case XmlTypeCode.AnyUri:
                                return XQItemType.ANYURI;
                            case XmlTypeCode.Base64Binary:
                                return XQItemType.BASE64_BINARY;
                            case XmlTypeCode.Boolean:
                                return XQItemType.BOOLEAN;
                            case XmlTypeCode.Duration:
                                return XQItemType.DURATION;
                            case XmlTypeCode.HexBinary:
                                return XQItemType.HEX_BINARY;
                            case XmlTypeCode.Language:
                                return XQItemType.LANGUAGE;
                            case XmlTypeCode.NCName:
                                return XQItemType.NCNAME;
                            case XmlTypeCode.NmToken:
                                return XQItemType.NMTOKEN;
                            case XmlTypeCode.Notation:
                                return XQItemType.NOTATION;
                            case XmlTypeCode.QName:
                                return XQItemType.QNAME;
                            case XmlTypeCode.String:
                                return XQItemType.STRING;
                            case XmlTypeCode.NormalizedString:
                                return XQItemType.NORMALIZED_STRING;
                            case XmlTypeCode.AnyAtomicType:
                                Node nilAttr = this.GetAttributeNode(Function.nil);
                                bool isNil = false;
                                if (nilAttr != null) {
                                    string nil = nilAttr.StringValue.Trim();
                                    isNil = nil == "true" || nil == "1";
                                }
                                if (!isNil) {
                                    string text = this.StringValue;
                                    NameTable nameTable = new NameTable();
                                    XdmNamespaceManager mgr = new XdmNamespaceManager(this);
                                    object value = schemaInfo.DataType.ParseValue(text, nameTable, mgr);
                                    if (value is DateTime) {
                                        if (text.Contains("T")) {
                                            return XQItemType.DATE_TIME;
                                        } else {
                                            return XQItemType.DATE;
                                        }
                                    }
                                }
                                break;
                            default:
                                //TODO coshare;
                                break;
                        }
                    }
                    return DtNodeType.getTypeByKind(NodeType);
                } catch (DataModelException e) {
                    throw WrapDMException(e);
                }
            }

        }
        virtual public bool ValueAsBoolean {
            get {
                //HACK CoShare
                try {
                    IXdmSchemaInfo schemaInfo = this.SchemaInfo;
                    if (schemaInfo != null && schemaInfo.DataType != null) {
                        switch (schemaInfo.DataType.TypeCode) {
                            case XmlTypeCode.Boolean:
                                string value = this.StringValue.Trim();
                                return value == "true" || value == "1";
                            default:
                                return ValueAsString.Length != 0; // OPTIM
                        }
                    }
                } catch (DataModelException e) {
                    //throw WrapDMException(e);
                    return false;
                }

                return ValueAsString.Length != 0; // OPTIM
            }

        }
        virtual public float ValueAsFloat {
            get {
                return Conversion.toFloat(ValueAsString);
            }

        }
        virtual public double ValueAsDouble {
            get {
                return Conversion.toDouble(ValueAsString);
            }

        }
        virtual public double DoubleByRules {
            //  redefined in FONIDataModel

            get {
                return System.Double.NaN;
            }

        }
        virtual public long ValueAsInteger {
            get {
                return Conversion.toInteger(ValueAsString);
            }

        }
        virtual public System.Decimal ValueAsDecimal {
            get {
                return Conversion.toDecimal(ValueAsString, true);
            }

        }
        virtual public QName ValueAsQName {
            get {
                #region CoShare Extensions
                IXdmSchemaInfo schemaInfo = this.SchemaInfo;
                if (schemaInfo != null && schemaInfo.DataType != null && schemaInfo.DataType.TypeCode == XmlTypeCode.QName) {
                    string prefix, namespaceURI;
                    string text = this.StringValue.Trim();
                    int index = text.IndexOf(':');
                    if (index != -1) {
                        prefix = text.Substring(0, index);
                        namespaceURI = this.GetNamespaceOfPrefix(prefix);
                        if (namespaceURI == null)
                            throw new EvaluationException("invalid qname");
                        return IQName.GetQName(namespaceURI, text.Substring(index + 1));
                    } else {
                        namespaceURI = this.GetNamespaceOfPrefix(string.Empty);
                        if (namespaceURI == null)
                            throw new EvaluationException("invalid qname");
                        return IQName.GetQName(namespaceURI, text);
                    }
                }
                #endregion
                throw new EvaluationException("Node not convertible to QName");
            }
            // ATTENTION: wrong
            //    public QName getQName()
            //        throws EvaluationException
            //    {
            //        try {
            //            return getNodeName();
            //        }
            //        catch (DataModelException e) {
            //            throw wrapDMException(e);
            //        }
            //    }

        }

        virtual public XQDate ValueAsDate {
            get {
                return XQDate.parseDate(ValueAsString.Trim());
            }

        }

        virtual public XQDateTime ValueAsDateTime {
            get {
                return XQDateTime.ParseDateTime(ValueAsString.Trim());
            }

        }

        public virtual BasicNode FirstChild {
            get {
                return GetFirstChild();
            }
        }

        Node Node.FirstChild {
            get {
                return GetFirstChild();
            }
        }

        /// <summary>
        /// Returns the next Node in document order. If this node has children,
        /// the next node is the first child; otherwise this is the first of the
        /// following nodes (in the sense of the XPath axis 'following').
        /// </summary>
        /// <value>The next node.</value>
        /// <returns> the next node, or null if the node has no successor.
        /// </returns>
        /// <throws>  DataModelException <a href="#cex">common causes</a>
        /// </throws>
        virtual public Node NextNode {
            get {
                return NodeNext();
            }

        }
        virtual public Node FollowingNode {
            get {
                return NodeAfter();
            }
        }
        public abstract XdmNodeType NodeType { get; }
        public abstract string StringValue { get; }
        public abstract string BaseURI { get; }
        public abstract System.Qizx.Api.QName NodeName { get; }
        public abstract string ValueAsString { get; }
        public virtual System.Qizx.Api.Node OwnerDocument { get { return this.GetOwnerDocument(); } }
        public abstract System.Qizx.Api.Util.XQDuration Duration { get; }
        public abstract string NodeKind { get; }
        public abstract System.Object Object { get; }
        public virtual System.Qizx.Api.Util.XQDateTimeBase ValueAsMoment {
            get {
                IXdmSchemaInfo schemaInfo = this.SchemaInfo;
                if (schemaInfo != null) {
                    string text = this.StringValue;
                    if (text != null) text = text.Trim();
                    switch (schemaInfo.DataType.TypeCode) {
                        case XmlTypeCode.Date:
                            return XQDate.parseDate(text);
                        case XmlTypeCode.DateTime:
                            return XQDateTime.ParseDateTime(text);
                        case XmlTypeCode.AnyAtomicType:
                            object value = schemaInfo.DataType.ParseValue(text, null, null);
                            if (value is DateTime) {
                                if (text.Contains("T")) {
                                    return XQDateTime.ParseDateTime(text);
                                } else {
                                    return XQDate.parseDate(text);
                                }
                            }
                            break;
                    }
                }
                // Should not be called
                throw new EvaluationException("cannot convert node to date or time");
            }
        }
        public abstract string DocumentURI { get; }
        public abstract int AttributeCount { get; }
        public abstract char[] CharValue { get; }
        public const int ATOM_BITSET = 13;

        public override string ToString() {
            try {
                string s = NodeKind + "(";
                switch (NodeType) {

                    case System.Qizx.Api.XdmNodeType.Element:
                        return "Element(" + NodeName + ")";

                    case System.Qizx.Api.XdmNodeType.Attribute:
                        return "Attr(" + NodeName + ")= " + StringValue;

                    case System.Qizx.Api.XdmNodeType.ProcessingInstruction:
                        return "PI(" + NodeName + ")= " + StringValue + ")";

                    default:
                        return NodeKind + "(" + StringValue + ")";

                }
            } catch (DataModelException e) {
                return "Node(?)";
            }
        }

        public virtual bool IsNode {
            get { return true; }
        }

        public virtual bool IsElement {
            get { return NodeType == System.Qizx.Api.XdmNodeType.Element; }
        }

        public virtual Node GetNode() {
            return this;
        }

        public virtual void Export(XMLPushStream writer) {
            writer.putNodeCopy(this, 0);
        }

        public abstract BasicNode NextSibling {
            get;
        }

        Node Node.NextSibling { get { return this.NextSibling; } }

        internal virtual BasicNode GetFirstChild() {
            return null;
        }
        internal virtual Node GetLastChild() { return null; }



        /// <summary>
        /// Nodes the next.
        /// </summary>
        /// <returns></returns>
        internal virtual BasicNode NodeNext() {
            BasicNode kid = GetFirstChild();
            return kid != null ? kid : NodeAfter();
        }

        internal virtual BasicNode NodeAfter() {
            BasicNode node = this, nextNode;
            while ((nextNode = node.NextSibling) == null) {
                Node parent = node.ParentNode;
                if (parent == null)
                    return null;
                node = (BasicNode)parent;
            }
            return nextNode;
        }

        public Node PreviousSibling { get { return GetPreviousSibling(); } }

        internal virtual Node GetPreviousSibling() {
            return null;
        }



        public virtual int StringValueCompareTo(Node that, System.Globalization.CompareInfo collator) {
            string s1 = this.StringValue, s2 = that.StringValue;
            return collator != null ? collator.Compare(s1.ToString(), s2.ToString()) : String.CompareOrdinal(s1, s2);
        }

        public abstract NodeSequenceBase GetParent(NodeFilter nodeFilter);

        public abstract NodeSequenceBase GetAncestors(NodeFilter nodeFilter);

        public abstract NodeSequenceBase GetAncestorsOrSelf(NodeFilter nodeFilter);

        public abstract NodeSequenceBase GetAttributes(NodeFilter nodeFilter);

        public abstract NodeSequenceBase GetChildren(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetDescendants(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetDescendantsOrSelf(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetFollowing(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetFollowingSiblings(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetPreceding(NodeFilter NodeFilter);

        public abstract NodeSequenceBase GetPrecedingSiblings(NodeFilter NodeFilter);
        internal virtual Node GetOwnerDocument() { return null; }

        public abstract int DocPosition();

        /// <summary> Returns the nodes matching an ID/IDREF attribute.</summary>
        /// <param name="id">value of attribute
        /// </param>
        /// <param name="idref">true if IDREF attribute
        /// </param>
        /// <throws>  DataModelException </throws>
        public abstract BasicNode[] GetIdMatchingNodes(string id, bool idref);

        /// <summary> Adds all in-scope namespaces of a node to a context.</summary>
        /// <param name="nsContext">a context into which namespaces are added locally
        /// </param>
        public abstract void AddInScopeNamespacesTo(NamespaceContext nsContext);

        /// <summary> Adds namespaces defined locally on an element to a context.</summary>
        /// <param name="nsContext">a context into which namespaces are added locally
        /// </param>
        /// <returns> the number of namespace declaration actually found
        /// </returns>
        /// <throws>  DataModelException </throws>
        public abstract int AddNamespacesTo(NamespaceContext nsContext);

        /// <summary>
        /// Adds the namespaces actually used by an element and its attributes to a
        /// context.
        /// </summary>
        /// <param name="nsContext">a context into which namespaces are added locally</param>
        /// <returns>
        /// the number of namespace declaration actually found
        /// </returns>
        public abstract int AddUsedNamespacesTo(NamespaceContext nsContext);

        internal static EvaluationException WrapDMException(DataModelException e) {
            return new EvaluationException(e.getErrorCode(), "data model error: " + e.Message, e);
        }
        public abstract bool DeepEquals(System.Qizx.XQuery.XQItem param1, System.Qizx.XQuery.ComparisonContext param2);
        public abstract System.Qizx.Api.XMLPullStream ExportNode();
        public abstract System.Qizx.Api.Node ParentNode { get; }
        public virtual string Prefix {
            get {
                QName name = this.NodeName;
                if (name == null) return string.Empty;
                string prefix = name.Prefix;
                if (string.IsNullOrEmpty(prefix)) {
                    return this.GetPrefixOfNamespace(name.Namespace);
                }
                return string.Empty;
            }
            set {
                //TODO
            }
        }
        public virtual string LocalName {
            get {
                QName name = this.NodeName;
                if (name != null) return name.Name;
                return string.Empty;
            }
        }
        public abstract string GetPrefixOfNamespace(string param1);
        public abstract bool DeepEquals(System.Qizx.Api.Node param1, System.Globalization.CompareInfo param2);
        public abstract int DocumentOrderCompareTo(System.Qizx.Api.Node param1);
        public abstract System.Qizx.Api.Node GetAttributeNode(System.Xml.XmlQualifiedName param1);
        public abstract string GetNamespaceOfPrefix(string param1);
        public abstract System.Qizx.Xdm.BasicNode GetBasicNode();
        public abstract int CompareTo(System.Qizx.XQuery.XQItem param1, System.Qizx.XQuery.ComparisonContext param2, int param3);
        public abstract bool Contains(System.Qizx.Api.Node param1);
        public abstract System.Qizx.Api.Node[] Attributes { get; }
        public virtual bool IsReadOnly { get { return false; } }

        /// <summary>
        /// Gets a value indicating whether this node has any child nodes. 
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has child nodes; otherwise, <c>false</c>.
        /// </value>
        public virtual bool HasChildNodes { get { return GetLastChild() != null; } }

        /// <summary>
        /// Gets or sets the concatenated values of the node and all its child nodes.
        /// </summary>
        /// <value>The inner text.</value>
        public abstract string InnerText { get; set; }

        /// <summary>
        /// Gets or sets the markup representing only the child nodes of this node.
        /// </summary>
        /// <value>The inner XML.</value>
        public abstract string InnerXml { get; set; }

        /// <summary>
        /// Gets the markup representing this node and all its child nodes.
        /// </summary>
        /// <value>The outer XML.</value>
        public abstract string OuterXml { get; }

        public Node LastChild { get { return GetLastChild(); } }

        public virtual void WriteTo(XmlWriter w) { }
        public virtual void WriteContentTo(XmlWriter w) { }

        /// <summary>
        /// When overridden in a derived class, gets the qualified name of the node.
        /// </summary>
        /// <value>The name.</value>
        public virtual string Name {
            get {
                QName name = this.NodeName;
                if (name == null) return string.Empty;
                string prefix = name.Prefix;
                if (string.IsNullOrEmpty(prefix)) {
                    prefix = this.GetPrefixOfNamespace(name.Namespace);
                }
                if (string.IsNullOrEmpty(prefix)) return name.Name;
                return prefix + ":" + name.Name;
            }
        }
        /// <summary>
        /// Gets the namespace URI of this node.
        /// </summary>
        /// <value>The namespace URI.</value>
        public virtual string NamespaceURI {
            get {
                QName name = this.NodeName;
                if (name != null) return name.Namespace;
                return string.Empty;
            }
        }

        public virtual ItemSequence SelectItems(string xpath, IXmlNamespaceResolver resolver) {
            string uri = this.DocumentURI;
            if (string.IsNullOrEmpty(uri)) uri = @"C:\";
            XQuerySessionManager manager = new XQuerySessionManager(new Uri(uri));
            XQuerySession session = manager.CreateSession();

            Expression expression = session.CompileExpression(xpath, resolver);
            expression.CurrentItem = this;

            return expression.Evaluate();
        }

        public virtual ItemSequence SelectItems(string xpath) {

            Node focus = this;
            if (this.NodeType == XdmNodeType.Document) {
                for (Node curr = this.FirstChild; curr != null; curr = curr.NextSibling) {
                    if (curr.NodeType == XdmNodeType.Element) {
                        focus = curr; break;
                    }
                }
            }
            XdmNamespaceManager resolver = new XdmNamespaceManager(focus);

            return SelectItems(xpath, resolver);
        }

        public Item SelectSingleItem(string xpath) {
            ItemSequence sequence = this.SelectItems(xpath);
            if (sequence != null && sequence.MoveToNextItem()) {
                return sequence.CurrentItem;
            }
            return null;
        }

        public Item SelectSingleItem(string xpath, IXmlNamespaceResolver resolver) {
            ItemSequence sequence = this.SelectItems(xpath, resolver);
            if (sequence != null && sequence.MoveToNextItem()) {
                return sequence.CurrentItem;
            }
            return null;
        }

        public static bool operator ==(BasicNode a, BasicNode b) {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b)) {
                return true;
            }

            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null)) {
                return false;
            }

            // Return true if the fields match:
            return false;
        }

        public static bool operator !=(BasicNode a, BasicNode b) {
            return !(a == b);
        }

        internal bool IsRooted {
            get {
                for (Node node = this.ParentNode; node != null; node = node.ParentNode) {
                    if (node.NodeType == XdmNodeType.Document) {
                        return true;
                    }
                }
                return false;
            }
        }
    }
}